窗口函数

概述

窗口函数(Window Functions)是SQL中一个强大的工具,允许你在查询结果集上执行计算,而不需要将结果集分组或聚合。窗口函数可以让你在同一查询中同时访问多行数据,这在处理排名、累计和移动平均等问题时非常有用。

理论解释

什么是窗口函数?

窗口函数是一种特殊的SQL函数,它对一组行(称为“窗口”)进行计算,并返回一个值。与聚合函数不同,窗口函数不会将行分组为单个输出行;相反,它们为每一行返回一个结果。

窗口函数的语法

窗口函数的基本语法如下:

function_name (expression) OVER (
    [PARTITION BY partition_expression]
    [ORDER BY sort_expression]
    [ROWS BETWEEN frame_start AND frame_end]
)
  • function_name: 窗口函数的名称,如 ROW_NUMBER, RANK, SUM, AVG 等。
  • expression: 要计算的表达式。
  • PARTITION BY: 可选,用于将数据分组。
  • ORDER BY: 可选,用于指定窗口内的排序顺序。
  • ROWS BETWEEN: 可选,用于定义窗口的边界。

常见的窗口函数

  • ROW_NUMBER(): 为每一行分配一个唯一的序号。
  • RANK(): 为每一行分配一个排名,相同的值会有相同的排名,但会跳过后续的排名。
  • DENSE_RANK(): 类似于 RANK(),但不会跳过后续的排名。
  • SUM(), AVG(), MIN(), MAX(): 这些聚合函数也可以作为窗口函数使用。

代码示例

示例1:使用 ROW_NUMBER()

假设我们有一个名为 sales 的表,包含销售数据:

CREATE TABLE sales (
    id SERIAL PRIMARY KEY,
    product_name VARCHAR(100),
    sale_date DATE,
    amount DECIMAL(10, 2)
);

INSERT INTO sales (product_name, sale_date, amount) VALUES
('Product A', '2023-01-01', 100.00),
('Product B', '2023-01-02', 200.00),
('Product A', '2023-01-03', 150.00),
('Product B', '2023-01-04', 250.00),
('Product A', '2023-01-05', 300.00);

我们可以使用 ROW_NUMBER() 为每一行分配一个序号:

SELECT 
    product_name, 
    sale_date, 
    amount,
    ROW_NUMBER() OVER (ORDER BY sale_date) AS row_num
FROM sales;

输出:

 product_name | sale_date  | amount | row_num 
--------------+------------+--------+---------
 Product A    | 2023-01-01 |  100.00 |       1
 Product B    | 2023-01-02 |  200.00 |       2
 Product A    | 2023-01-03 |  150.00 |       3
 Product B    | 2023-01-04 |  250.00 |       4
 Product A    | 2023-01-05 |  300.00 |       5

示例2:使用 RANK() 和 DENSE_RANK()

假设我们有一个名为 scores 的表,包含学生的考试成绩:

CREATE TABLE scores (
    student_id INT,
    subject VARCHAR(50),
    score INT
);

INSERT INTO scores (student_id, subject, score) VALUES
(1, 'Math', 90),
(2, 'Math', 85),
(3, 'Math', 90),
(4, 'Math', 80),
(5, 'Math', 75);

我们可以使用 RANK() 和 DENSE_RANK() 来计算每个学生的排名:

SELECT 
    student_id, 
    subject, 
    score,
    RANK() OVER (ORDER BY score DESC) AS rank,
    DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank
FROM scores;

输出:

 student_id | subject | score | rank | dense_rank 
------------+---------+-------+------+------------
          1 | Math    |    90 |    1 |          1
          3 | Math    |    90 |    1 |          1
          2 | Math    |    85 |    3 |          2
          4 | Math    |    80 |    4 |          3
          5 | Math    |    75 |    5 |          4

示例3:使用 SUM() 作为窗口函数

我们可以使用 SUM() 来计算累计和:

SELECT 
    product_name, 
    sale_date, 
    amount,
    SUM(amount) OVER (ORDER BY sale_date) AS cumulative_sum
FROM sales;

输出:

 product_name | sale_date  | amount | cumulative_sum 
--------------+------------+--------+---------------
 Product A    | 2023-01-01 |  100.00 |         100.00
 Product B    | 2023-01-02 |  200.00 |         300.00
 Product A    | 2023-01-03 |  150.00 |         450.00
 Product B    | 2023-01-04 |  250.00 |         700.00
 Product A    | 2023-01-05 |  300.00 |        1000.00

实践练习

练习1:计算每个产品的累计销售量

使用 SUM() 窗口函数,计算每个产品的累计销售量。

SELECT 
    product_name, 
    sale_date, 
    amount,
    SUM(amount) OVER (PARTITION BY product_name ORDER BY sale_date) AS cumulative_sum
FROM sales;

练习2:计算每个学生的排名

使用 RANK() 和 DENSE_RANK() 窗口函数,计算每个学生在不同科目中的排名。

SELECT 
    student_id, 
    subject, 
    score,
    RANK() OVER (PARTITION BY subject ORDER BY score DESC) AS rank,
    DENSE_RANK() OVER (PARTITION BY subject ORDER BY score DESC) AS dense_rank
FROM scores;

练习3:计算移动平均

使用 AVG() 窗口函数,计算每个产品的3天移动平均销售量。

SELECT 
    product_name, 
    sale_date, 
    amount,
    AVG(amount) OVER (
        PARTITION BY product_name 
        ORDER BY sale_date 
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg
FROM sales;

总结

窗口函数是SQL中一个非常强大的工具,允许你在查询结果集上执行复杂的计算。通过使用 ROW_NUMBER(), RANK(), DENSE_RANK(), SUM(), AVG() 等窗口函数,你可以轻松地处理排名、累计和移动平均等问题。通过实践练习,你可以更好地理解和掌握这些功能。